﻿#include "study12.h"
#include "MyVector.hpp"
//#include <windows.h>
// C/C++ 标准库学习网站：https://legacy.cplusplus.com
// C/C++ 微软(Visual Studio)中文学习网站：https://learn.microsoft.com/zh-cn/cpp/cpp/?view=msvc-170

/*
移动语义的使用主要是在构造函数中：
	通过右值引用（&&）来接收一个临时对象，这样可以避免拷贝构造函数的调用，从而实现移动语义；
	原理是：通过右值引用构造函数将临时对象移动到当前对象，并置空临时对象，在外部使用时显示调用移动构造函数（std::move）
	std::move 是一个函数模板，它接受一个左值引用，并返回一个右值引用，作用是将左值转换为右值。
	std::move 三法则：如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符；C++五法则为了支持移动语义, 又增加了移动构造函数和移动赋值运算符。

类的数据迭代器：
	包含 auto begin() const { return data_; } // 定义 const 成员函数，返回指向数组首元素的指针
	包含 auto end() const { return data_ + size_; } // 定义 const 成员函数，返回指向数组尾元素的指针
	包含 auto cbegin() const { return data_; } // 定义 const 成员函数，返回指向数组首元素的 const 指针
	包含 auto cend() const { return data_ + size_; } // 定义 const 成员函数，返回指向数组尾元素的 const 指针

内联函数（编译器会有语法检查）关键字 inline：
	inline 关键字用来修饰函数，函数必须定义（有语句块），才能被内联。使得函数在调用点展开，而不是在定义点。
	内联函数的原理是在函数预处理阶段将函数体语句内联到调用点（避免函数调用），优点是减少函数调用的开销，提高程序运行效率。
	但是，内联函数的定义不能太大、不能有循环、递归等复杂语句，否则编译器会将其当做普通函数处理。

constexpr 关键字：
	constexpr 关键字用来修饰常量表达式，可以提高程序的运行效率，因为编译器可以对 constexpr 表达式进行常量折叠（即编译器会将 constexpr 表达式视为类似于内联函数的操作！所以在预处理时若能求出其值，则会把表达式调用替换成结果值。），从而使得程序运行效率更高。
	constexpr 修饰函数（有点像内联函数）：
		constexpr 返回类型 函数名(参数列表) {...} 函数返回值变成了常量类型，可以用于常量表达式的计算。注意内联函数不行
		但是，constexpr 函数的函数体尽量只包含一个 return 语句，多个可能会编译出错；函数体可以包含其他语句，但是不能是运行期语句，只能是编译期语句。
	类的构造函数中使用 constexpr 关键字：
		保证传递给该构造函数的所有参数都是 constexpr，那么产生的对象的所有成员都是 constexpr。该对象是 constexpr 对象了，可用于只使用 constexpr 的场合。
		注意 constexpr 构造函数的函数体必须为空，所有成员变量的初始化都放到初始化列表中。
	constexpr 指针：
		与 const 不同，在 constexpr 声明中如果定义了一个指针，限定符 constexpr 仅对指针有效，与指针所指对象无关。
		constexpr 是一种很强的约束，更好的保证程序的正确定语义不被破坏；编译器可以对 constexpr 代码进行非常大的优化，例如：将用到的 constexpr 表达式直接替换成结果, 相比宏来说没有额外的开销。

noexcept 关键字：
	noexcept 关键字用来修饰函数，表示该函数不会抛出异常，可以提高程序的运行效率。
	noexcept 关键字的作用：
		告诉编译器，该函数不会抛出异常，可以优化程序的运行效率。
		如果函数不抛出异常，则可以不进行异常处理，从而提高程序的运行效率。
		如果函数抛出异常，则会导致程序崩溃，因此 noexcept 关键字不能用于构造函数、析构函数、拷贝构造函数、移动构造函数、拷贝赋值运算符、移动赋值运算符。

static_assert 关键字：
	static_assert 用来在编译时进行断言，如果表达式为 false，则编译出错。
	static_assert 语法：static_assert(表达式, 错误信息);
	static_assert 常用用法举例：
		static_assert(sizeof(int) == 4, "int 类型大小错误"); // 断言 int 类型大小为 4
		static_assert(sizeof(long) == 8, "long 类型大小错误"); // 断言 long 类型大小为 8
		static_assert(std::is_same_v<int, long>, "int 类型与 long 类型不同"); // 断言 int 类型与 long 类型相同
		static_assert(std::is_same_v<int, long long>, "int 类型与 long long 类型不同"); // 断言 int 类型与 long long 类型相同
		static_assert(std::is_same_v<int, char>, "int 类型与 char 类型不同"); // 断言 int 类型与 char 类型不同

volatile 关键字：
	volatile 关键字用来修饰变量，使得该变量的读写操作不会被编译器优化，从而保证程序的正确性。
	volatile 关键字主要用于多线程程序中，当一个变量被声明为 volatile 时，编译器不会对其进行优化，即使它在程序中只被读或写一次，也会将它存放在内存中，以防止其他线程的修改。
	volatile 关键字的作用：
		保证变量的可见性，避免多个线程同时修改变量，导致数据不一致。
		防止编译器对变量进行优化，保证程序的正确性。
		保证变量的原子性，避免指令重排序。

可变参数模版：
	template<typename... Args> 函数名(Args&&... args) {...}
	可变参数模版可以接受任意数量的实参，并将它们作为参数传递给函数。这里的...用于解析参数
	可通过 std::forward 关键字来转发参数，从而实现移动语义:
		std::forward<Args>(args)... // 转发参数，返回右值引用
	注意：
		可变参数模版的函数体内不能有循环、递归等复杂语句，否则编译器会将其当做普通函数处理。
	配合 new 关键字使用可以避免移动语义的使用：
		new(左值引用) 函数名(右值引用) // 直接将右值转为左值，不调用移动赋值运算符函数

C++ 的动态内存管理还有 ::operator new 和 ::operator delete 关键字，用于自定义内存分配和释放。
	::operator new 用于分配内存:
		::operator new(size_t size) // 调用系统的 malloc 函数分配内存
		::operator new[](size_t size) // 调用系统的 malloc 函数分配内存，并返回数组首地址
		::operator new(size_t size, const std::nothrow_t& tag) // 调用系统的 malloc 函数分配内存，并返回 nullptr，表示分配失败
	::operator delete 用于释放内存:
		::operator delete(void* ptr, size_t size) // 调用系统的 free 函数释放内存，释放指定大小的内存
		::operator delete(void* ptr, const std::nothrow_t& tag) // 调用系统的 free 函数释放内存，忽略异常

可以使用开源的 imgui 库来进行图形界面开发，它提供了丰富的控件，可以快速实现图形界面程序。
	网址：https://github.com/ocornut/imgui
	git 克隆（选择想存放的目录）命令：
		git clone --recursive https://github.com/ocornut/imgui.git[ -b docking 新建目录名]
		这里的 -b docking 选项是为了克隆 docking 分支，如果不指定则默认克隆 master 分支。
	git 更新命令：
		git pull origin master // 更新 master 分支
		git submodule update --init --recursive // 更新子模块
	imgui 库的使用方法：
		参考官方文档：https://github.com/ocornut/imgui/tree/master/docs
*/
#define pt std::cout
#define ed std::endl

int main()
{
	// 测试移动语义、操作符
	print_start("测试移动语义、操作符部分");
	test_move_semantics();
	print_end("测试移动语义、操作符部分结束");

	// 测试内联函数、constexpr
	print_start("测试内联函数、constexpr部分");
	test_constexpr_inline();
	print_end("测试内联函数、constexpr部分结束");

	// 模仿 std::array 实现自己 array 数组
	{
		print_start("测试自定义的 array 数组部分");
		// 测试标准库中的 std::array 的一些操作
		std::array<int, 18> arr1 = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 160, 170 }; // 通用构造函数调用，不知道怎么实现！
		static_assert(std::is_same_v<decltype(arr1)::value_type, int>, "value_type 类型错误"); // 类型检查
		for (int i : arr1)
			std::cout << i << "   ";
		pt << "\n----------------------------------------------------\n";

		// 测试自定义的 array 数组的一些操作
		self::MyArray<int, 10> arr2;
		pt << "sizeof arr2 = " << sizeof arr2 << ed; // 输出 sizeof arr2 = 40
		//memset(arr2.get_data(), 0, arr2.my_size() * sizeof(int));
		//memset(&arr2[0], 0, arr2.my_size() * sizeof(int));
		std::fill(arr2.get_data(), arr2.get_data() + arr2.my_size(), 0); // 数组元素全部置零，同上
		static_assert(arr2.my_size() <= 10, "数组太长"); // 静态断言，自定义的数组大小检查
		const auto& ref = arr2;
		for (int i = 0; i < arr2.my_size(); ++i)
			pt << ref[i] << "   ";
		pt << ed;
		for (int i = 0; i < arr2.my_size(); ++i)
			arr2[i] = i * 2; // 数组元素全部赋值
		for (int& i : arr2) // 遍历数组元素
			pt << i << "   ";
		/*for (auto i = arr2.begin(); i != arr2.end(); ++i) // 遍历数组元素
			pt << *i << "   "; */
		pt << ed;
		self::MyArray<std::string, 2> arr3; // 移动语义，自定义的数组实现了移动语义
		arr3[0] = "测试第一个字符串";
		arr3[1] = "测试第二个字符串";
		//pt << "arr3[0] = " << arr3[0] << ed; // 输出 arr3[0] = 测试第一个字符串
		//pt << "arr3[1] = " << arr3[1] << ed; // 输出 arr3[1] = 测试第二个字符串
		/*for (const auto& s : arr3) // 遍历数组元素
			pt << s << "   "; */
		for (auto s = arr3.begin(); s < arr3.end(); ++s)
			pt << *s << "   ";
		pt << ed;
	}
	print_end("测试自定义的 array 数组部分");

	// 测试 volatile 关键字
	print_start("测试 volatile 关键字部分");
	volatile int a = 10;
	a = 20;
	pt << "a = " << a << ed; // 输出 a = 20
	volatile int b = a;
	b = 30;
	pt << "b = " << b << ", a = " << a << ed; // 输出 b = 30
	print_end("测试 volatile 关键字部分");

	// 模仿 std::vector 实现自己 MyVector 数组
	{
		print_start("测试自定义的 vector 数组部分");
		// 测试标准库中的 std::vector 的一些操作
		std::vector<const char*> vec1;
		vec1.push_back("hello");
		vec1.push_back("world");
		for (const auto& s : vec1)
			pt << s << " ";
		pt << "\n-------------------------------------------------\n";

		self::MyVector<const char*> vec2;
		vec2.add_end("my ");
		vec2.add_end("name ");
		vec2.add_end("is ");
		vec2.add_end("Tom.");
		//pt << vec2[1] << vec2.get_len() << ed;
		vec2[1] = "last name";
		for (const auto& s : vec2)
			pt << s << " ";
		pt << ed;
		print_my_vector(vec2); // 输出 my last name is Tom.

		self::MyVector<self::coord> vec3;
		vec3.add_end(self::coord{ 2, 2, 5 });
		vec3.add_end(self::coord());
		pt << vec3.get_data()[0].x << ed; // 输出 2 奇怪的写法
		print_my_vector(vec3); // 输出 (1, 1, 1) (2, 2, 5) (0, 0, 0)

		self::MyVector<self::coord> vec4;
		vec4.move_end(self::coord());
		vec4.move_end(self::coord{ 4, 5, 6 });
		vec4.move_end(self::coord(8.0f));
		print_my_vector(vec4); // 输出 (0, 0, 0) (4, 5, 6) (8, 8, 8)

		self::MyVector<self::coord> vec5;
		{
			self::coord ct1(0.0f);
			vec5.emplace_end(ct1);
		}
		vec5.emplace_end(1.0f, 2.0f, 3.0f);
		vec5.emplace_end(5.0f);
		print_my_vector(vec5); // 输出 (0, 0, 0) (1, 2, 3) (5, 5, 5)
		vec5.pop_end();
		vec5.pop_end();
		vec5.emplace_end(2.0f, 3.0f, 4.0f);
		print_my_vector(vec5); // 输出 (0, 0, 0) (1, 2, 3)
		vec5.clean_data();
		print_my_vector(vec5);
		vec5.emplace_end(10.0f, 10.0f, 10.0f);
		print_my_vector(vec5); // 输出 (10, 10, 10)

		// 标准库中的 std::vector 测试
		std::vector<self::coord> vec6;
		vec6.emplace_back(1.0f, 2.0f, 3.0f);
		vec6.emplace_back(4.0f, 5.0f, 6.0f);
		vec6.emplace_back(7.0f, 8.0f, 9.0f);
		vec6.pop_back();
		vec6.pop_back();
		vec6.push_back(self::coord(10.0f, 11.0f, 12.0f));
		for (const auto& c : vec6)
			pt << "(" << c.x << ", " << c.y << ", " << c.z << ") ";
		pt << "\n------------------------------------------\n";
	}
	print_end("测试自定义的 vector 数组部分");

	return 0;
}
